// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ssa

import (
	"bytes"
	"cmd/internal/src"
	"fmt"
	"hash/crc32"
	"internal/buildcfg"
	"io"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"time"
)

// 编译是这个包的主要入口点。
// Compile修改f，以便在返回时：
// /·f中的所有值映射到目标体系结构的0或1汇编指令
// /·f的顺序。块的顺序是发出块的顺序
// /·b的顺序。值的顺序是发出每个块中的值
// /·f有一个非零的regAlloc字段
func Compile(f *Func) {
	// TODO:调试-设置标志以控制编译器的详细性，
	// 在哪个阶段之前/之后转储IR，等等。
	if f.Log() {
		f.Logf("compiling %s\n", f.Name)
	}

	var rnd *rand.Rand
	if checkEnabled {
		seed := int64(crc32.ChecksumIEEE(([]byte)(f.Name))) ^ int64(checkRandSeed)
		rnd = rand.New(rand.NewSource(seed))
	}

	// 如果出现死机，钩住打印函数和阶段
	phaseName := "init"
	defer func() {
		if phaseName != "" {
			err := recover()
			stack := make([]byte, 16384)
			n := runtime.Stack(stack, false)
			stack = stack[:n]
			if f.HTMLWriter != nil {
				f.HTMLWriter.flushPhases()
			}
			f.Fatalf("panic during %s while compiling %s:\n\n%v\n\n%s\n", phaseName, f.Name, err, stack)
		}
	}()

	// 运行所有过程
	if f.Log() {
		printFunc(f)
	}
	f.HTMLWriter.WritePhase("start", "start")
	if BuildDump[f.Name] {
		f.dumpFile("build")
	}
	if checkEnabled {
		checkFunc(f)
	}
	const logMemStats = false
	for _, p := range passes {
		if !f.Config.optimize && !p.required || p.disabled {
			continue
		}
		f.pass = &p
		phaseName = p.name
		if f.Log() {
			f.Logf("  pass %s begin\n", p.name)
		}
		// TODO:在此过程中捕获日志，将其添加到HTML 
		var mStart runtime.MemStats
		if logMemStats || p.mem {
			runtime.ReadMemStats(&mStart)
		}

		if checkEnabled && !f.scheduled {
			// 测试我们是否不依赖于值顺序，通过随机化
			// 每个块中值的顺序。见第18169期。
			for _, b := range f.Blocks {
				for i := 0; i < len(b.Values)-1; i++ {
					j := i + rnd.Intn(len(b.Values)-i)
					b.Values[i], b.Values[j] = b.Values[j], b.Values[i]
				}
			}
		}

		tStart := time.Now()
		p.fn(f)
		tEnd := time.Now()

		// 需要比“记录整个中间结果”更简单的东西。
		if f.Log() || f.HTMLWriter != nil {
			time := tEnd.Sub(tStart).Nanoseconds()
			var stats string
			if logMemStats {
				var mEnd runtime.MemStats
				runtime.ReadMemStats(&mEnd)
				nBytes := mEnd.TotalAlloc - mStart.TotalAlloc
				nAllocs := mEnd.Mallocs - mStart.Mallocs
				stats = fmt.Sprintf("[%d ns %d allocs %d bytes]", time, nAllocs, nBytes)
			} else {
				stats = fmt.Sprintf("[%d ns]", time)
			}

			if f.Log() {
				f.Logf("  pass %s end %s\n", p.name, stats)
				printFunc(f)
			}
			f.HTMLWriter.WritePhase(phaseName, fmt.Sprintf("%s <span class=\"stats\">%s</span>", phaseName, stats))
		}
		if p.time || p.mem {
			// 有足够上下文的环绕计时信息，允许进行比较。
			time := tEnd.Sub(tStart).Nanoseconds()
			if p.time {
				f.LogStat("TIME(ns)", time)
			}
			if p.mem {
				var mEnd runtime.MemStats
				runtime.ReadMemStats(&mEnd)
				nBytes := mEnd.TotalAlloc - mStart.TotalAlloc
				nAllocs := mEnd.Mallocs - mStart.Mallocs
				f.LogStat("TIME(ns):BYTES:ALLOCS", time, nBytes, nAllocs)
			}
		}
		if p.dump != nil && p.dump[f.Name] {
			// 将函数转储到适当命名的文件
			f.dumpFile(phaseName)
		}
		if checkEnabled {
			checkFunc(f)
		}
	}

	if f.HTMLWriter != nil {
		// 确保我们将任何挂起的阶段写入html 
		f.HTMLWriter.flushPhases()
	}

	if f.ruleMatches != nil {
		var keys []string
		for key := range f.ruleMatches {
			keys = append(keys, key)
		}
		sort.Strings(keys)
		buf := new(bytes.Buffer)
		fmt.Fprintf(buf, "%s: ", f.Name)
		for _, key := range keys {
			fmt.Fprintf(buf, "%s=%d ", key, f.ruleMatches[key])
		}
		fmt.Fprint(buf, "\n")
		fmt.Print(buf.String())
	}

	// 压缩错误打印延迟
	phaseName = ""
}

// DumpFileForPhase根据函数名和阶段名创建文件，
// 如果不可能，则警告并返回零。
func (f *Func) DumpFileForPhase(phaseName string) io.WriteCloser {
	f.dumpFileSeq++
	fname := fmt.Sprintf("%s_%02d__%s.dump", f.Name, int(f.dumpFileSeq), phaseName)
	fname = strings.Replace(fname, " ", "_", -1)
	fname = strings.Replace(fname, "/", "_", -1)
	fname = strings.Replace(fname, ":", "_", -1)

	if ssaDir := os.Getenv("GOSSADIR"); ssaDir != "" {
		fname = filepath.Join(ssaDir, fname)
	}

	fi, err := os.Create(fname)
	if err != nil {
		f.Warnl(src.NoXPos, "Unable to create after-phase dump file %s", fname)
		return nil
	}
	return fi
}

// dumpFile根据阶段名和函数名创建一个文件
// 对文件进行转储，以避免在
// 输出之前缓冲巨大的字符串。
func (f *Func) dumpFile(phaseName string) {
	fi := f.DumpFileForPhase(phaseName)
	if fi != nil {
		p := stringFuncPrinter{w: fi}
		fprintFunc(p, f)
		fi.Close()
	}
}

type pass struct {
	name     string
	fn       func(*Func)
	required bool
	disabled bool
	time     bool            // 报告运行pass的时间
	mem      bool            // 报告mem stats以运行pass 
	stats    int             // pass报告自己的“stats”（例如，删除的分支）
	debug    int             // pass执行一些调试=1应该是错误测试友好的Warnl格式。
	test     int             // 通过特定的特别选项，如果函数名与
}

func (p *pass) addDump(s string) {
	if p.dump == nil {
		p.dump = make(map[string]bool)
	}
	p.dump[s] = true
}

func (p *pass) String() string {
	if p == nil {
		return "nil pass"
	}
	return p.name
}

// 在每个阶段之间运行一致性检查
var (
	checkEnabled  = false
	checkRandSeed = 0
)

// 调试输出
var IntrinsicsDebug int
var IntrinsicsDisable bool

var BuildDebug int
var BuildTest int
var BuildStats int
var BuildDump map[string]bool = make(map[string]bool) // 初始构建ssa后要转储的函数名

var GenssaDump map[string]bool = make(map[string]bool) // ssa转换为asm后要转储的函数名

// PhaseOption在指定的ssa阶段
// 如果成功，返回空字符串；如果失败，返回解释
// 错误的字符串。
// 还将检查阶段名称的版本是否匹配，并将“”替换为“”。
// 如果阶段名称以“~”开头，则用空格替换其余下划线
// 版本用作正则表达式以匹配阶段名称。
// 
// 证明有用的特殊情况：
// ssa/check/on启用每个阶段后的检查
// ssa/all/time启用所有阶段的时间报告
// 
// 请参阅gc/lex。去解剖选项字符串。
// 示例用法：
// 
// GO_GCFLAGS=-d=ssa/generic_cse/time，ssa/generic_cse/stats，ssa/generic_cse/debug=3/制作bash 
// 
// BOOT_GO_GCFLAGS=-d='ssa/~^*scc$/off'GO_GCFLAGS='-d=ssa/~^*scc美元/折/制作bash 
// 
func PhaseOption(phase, flag string, val int, valString string) string {
	switch phase {
	case "", "help":
		lastcr := 0
		phasenames := "    check, all, build, intrinsics, genssa"
		for _, p := range passes {
			pn := strings.Replace(p.name, " ", "_", -1)
			if len(pn)+len(phasenames)-lastcr > 70 {
				phasenames += "\n    "
				lastcr = len(phasenames)
				phasenames += pn
			} else {
				phasenames += ", " + pn
			}
		}
		return `PhaseOptions usage:

    go tool compile -d=ssa/<phase>/<flag>[=<value>|<function_name>]

where:

- <phase> is one of:
` + phasenames + `

- <flag> is one of:
    on, off, debug, mem, time, test, stats, dump, seed

- <value> defaults to 1

- <function_name> is required for the "dump" flag, and specifies the
  name of function to dump after <phase>

Phase "all" supports flags "time", "mem", and "dump".
Phase "intrinsics" supports flags "on", "off", and "debug".
Phase "genssa" (assembly generation) supports the flag "dump".

If the "dump" flag is specified, the output is written on a file named
<phase>__<function_name>_<seq>.dump; otherwise it is directed to stdout.

Examples:

    -d=ssa/check/on
enables checking after each phase

	-d=ssa/check/seed=1234
enables checking after each phase, using 1234 to seed the PRNG
used for value order randomization

    -d=ssa/all/time
enables time reporting for all phases

    -d=ssa/prove/debug=2
sets debugging level to 2 in the prove pass

Be aware that when "/debug=X" is applied to a pass, some passes
will emit debug output for all functions, and other passes will
only emit debug output for functions that match the current
GOSSAFUNC value.

Multiple flags can be passed at once, by separating them with
commas. For example:

    -d=ssa/check/on,ssa/all/time
`
	}

	if phase == "check" {
		switch flag {
		case "on":
			checkEnabled = val != 0
			debugPoset = checkEnabled // 在prove的数据结构
			return ""
		case "off":
			checkEnabled = val == 0
			debugPoset = checkEnabled
			return ""
		case "seed":
			checkEnabled = true
			checkRandSeed = val
			debugPoset = checkEnabled
			return ""
		}
	}

	alltime := false
	allmem := false
	alldump := false
	if phase == "all" {
		switch flag {
		case "time":
			alltime = val != 0
		case "mem":
			allmem = val != 0
		case "dump":
			alldump = val != 0
			if alldump {
				BuildDump[valString] = true
				GenssaDump[valString] = true
			}
		default:
			return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/all/{time,mem,dump=function_name})", flag, phase)
		}
	}

	if phase == "intrinsics" {
		switch flag {
		case "on":
			IntrinsicsDisable = val == 0
		case "off":
			IntrinsicsDisable = val != 0
		case "debug":
			IntrinsicsDebug = val
		default:
			return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/intrinsics/{on,off,debug})", flag, phase)
		}
		return ""
	}
	if phase == "build" {
		switch flag {
		case "debug":
			BuildDebug = val
		case "test":
			BuildTest = val
		case "stats":
			BuildStats = val
		case "dump":
			BuildDump[valString] = true
		default:
			return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/build/{debug,test,stats,dump=function_name})", flag, phase)
		}
		return ""
	}
	if phase == "genssa" {
		switch flag {
		case "dump":
			GenssaDump[valString] = true
		default:
			return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/genssa/dump=function_name)", flag, phase)
		}
		return ""
	}

	underphase := strings.Replace(phase, "_", " ", -1)
	var re *regexp.Regexp
	if phase[0] == '~' {
		r, ok := regexp.Compile(underphase[1:])
		if ok != nil {
			return fmt.Sprintf("Error %s in regexp for phase %s, flag %s", ok.Error(), phase, flag)
		}
		re = r
	}
	matchedOne := false
	for i, p := range passes {
		if phase == "all" {
			p.time = alltime
			p.mem = allmem
			if alldump {
				p.addDump(valString)
			}
			passes[i] = p
			matchedOne = true
		} else if p.name == phase || p.name == underphase || re != nil && re.MatchString(p.name) {
			switch flag {
			case "on":
				p.disabled = val == 0
			case "off":
				p.disabled = val != 0
			case "time":
				p.time = val != 0
			case "mem":
				p.mem = val != 0
			case "debug":
				p.debug = val
			case "stats":
				p.stats = val
			case "test":
				p.test = val
			case "dump":
				p.addDump(valString)
			default:
				return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase)
			}
			if p.disabled && p.required {
				return fmt.Sprintf("Cannot disable required SSA phase %s using -d=ssa/%s debug option", phase, phase)
			}
			passes[i] = p
			matchedOne = true
		}
	}
	if matchedOne {
		return ""
	}
	return fmt.Sprintf("Did not find a phase matching %s in -d=ssa/... debug option", phase)
}

// 编译器的过程列表
var passes = [...]pass{
	// TODO:将phielim和copyelim合并为一个过程？
	{name: "number lines", fn: numberLines, required: true},
	{name: "early phielim", fn: phielim},
	{name: "early copyelim", fn: copyelim},
	{name: "early deadcode", fn: deadcode}, // 删除生成的死代码，以避免在opt期间执行无意义的工作
	{name: "short circuit", fn: shortcircuit},
	{name: "decompose user", fn: decomposeUser, required: true},
	{name: "pre-opt deadcode", fn: deadcode},
	{name: "opt", fn: opt, required: true},               // 注意：一些通用规则知道opt通行证的名称。TODO:拆分所需规则和优化规则
	{name: "zero arg cse", fn: zcse, required: true},     // 合并OpSB值所需
	{name: "opt deadcode", fn: deadcode, required: true}, // 删除opt 
	{name: "generic cse", fn: cse},
	{name: "phiopt", fn: phiopt},
	{name: "gcse deadcode", fn: deadcode, required: true}, // 在cse和PHIOP 
	{name: "nilcheckelim", fn: nilcheckelim},
	{name: "prove", fn: prove},
	{name: "early fuse", fn: fuseEarly},
	{name: "decompose builtin", fn: decomposeBuiltIn, required: true},
	{name: "expand calls", fn: expandCalls, required: true},
	{name: "softfloat", fn: softfloat, required: true},
	{name: "dead auto elim", fn: elimDeadAutosGeneric},
	{name: "generic deadcode", fn: deadcode, required: true}, // 删除死区存储，否则会弄乱连锁店
	{name: "check bce", fn: checkbce},
	{name: "branchelim", fn: branchelim},
	{name: "late fuse", fn: fuseLate},
	{name: "dse", fn: dse},
	{name: "writebarrier", fn: writebarrier, required: true}, // 扩展写屏障操作
	{name: "insert resched checks", fn: insertLoopReschedChecks,
		disabled: !buildcfg.Experiment.PreemptibleLoops}, // 在循环中插入重新扫描的检查。
	{name: "lower", fn: lower, required: true},
	{name: "addressing modes", fn: addressingModes, required: false},
	{name: "lowered deadcode for cse", fn: deadcode}, // CSE之前的死代码避免了CSE再次激活死值
	{name: "lowered cse", fn: cse},
	{name: "elim unread autos", fn: elimUnreadAutos},
	{name: "tighten tuple selectors", fn: tightenTupleSelectors, required: true},
	{name: "lowered deadcode", fn: deadcode, required: true},
	{name: "checkLower", fn: checkLower, required: true},
	{name: "late phielim", fn: phielim},
	{name: "late copyelim", fn: copyelim},
	{name: "tighten", fn: tighten}, // 寄存器
	{name: "late deadcode", fn: deadcode},
	{name: "likelyadjust", fn: likelyadjust},
	{name: "late nilcheck", fn: nilcheckelim2},
	{name: "regalloc", fn: regalloc, required: true},   // 分配整型和浮点寄存器+堆栈插槽
	{name: "loop rotate", fn: loopRotate},
	{name: "stackframe", fn: stackframe, required: true},
	{name: "trim", fn: trim}, // 删除空块
}

// 仔细检查相位顺序约束。
// 本规范旨在记录不同阶段之间的订购要求。它不会覆盖上面的过程
// 列表。
type constraint struct {
	a, b string // a必须在b 
}

var passOrder = [...]constraint{
	// “插入重新调整的支票”使用mem之前到达，最好先清理商店。
	{"dse", "insert resched checks"},
	// insert resched checks添加包含通用指令的新块
	{"insert resched checks", "lower"},
	{"insert resched checks", "tighten"},

	// prove依赖公共子表达式消除来获得最大的好处。
	{"generic cse", "prove"},
	// 证明消除所有新的死块后的死码。
	{"prove", "generic deadcode"},
	// 死前的公共子表达式存储elim，这样当两个地址表达式相同时，我们就可以识别
	// 了。
	{"generic cse", "dse"},
	// cse显著提高了nilcheckelim的效率
	{"generic cse", "nilcheckelim"},
	// nilcheckelim 
	{"nilcheckelim", "generic deadcode"},
	// nilcheckelim生成普通基本块序列
	{"nilcheckelim", "late fuse"},
	// nilcheckelim依赖于opt重写用户的零检查
	{"opt", "nilcheckelim"},
	// STREAM将在尽可能多的值被删除时最有效
	{"generic deadcode", "tighten"},
	{"generic cse", "tighten"},
	// 检查BCE需要删除的值
	{"generic deadcode", "check bce"},
	// 在分解内置对象之前不要运行优化过程
	{"decompose builtin", "late opt"},
	// 分解内置是可能引入新浮点运算的最后一个过程，因此，在运行softfloat之后
	{"decompose builtin", "softfloat"},
	// 必须将元组选择器拧紧到生成器，并在调度
	{"tighten tuple selectors", "schedule"},
	// 在phi拧紧之前删除关键边，因此phi参数得到更好的放置
	{"critical", "phi tighten"},
	// 在关键边被删除之前不要布局块
	{"critical", "layout"},
	// regalloc要求删除所有关键边
	{"critical", "regalloc"},
	// regalloc要求一个块中的所有值都被调度
	{"schedule", "regalloc"},
	// 在降低后必须运行checkLower&随后的死代码elim 
	{"lower", "checkLower"},
	{"lowered deadcode", "checkLower"},
	// late nilcheck需要安排指令。
	{"schedule", "late nilcheck"},
	// flagalloc需要安排指令。
	{"schedule", "flagalloc"},
	// regalloc需要先分配标志。
	{"flagalloc", "regalloc"},
	// loopRotate会让regalloc感到困惑。
	{"regalloc", "loop rotate"},
	// stackframe需要了解溢出寄存器。
	{"regalloc", "stackframe"},
	// 需要先进行regalloc修整。
	{"regalloc", "trim"},
}

func init() {
	for _, c := range passOrder {
		a, b := c.a, c.b
		i := -1
		j := -1
		for k, p := range passes {
			if p.name == a {
				i = k
			}
			if p.name == b {
				j = k
			}
		}
		if i < 0 {
			log.Panicf("pass %s not found", a)
		}
		if j < 0 {
			log.Panicf("pass %s not found", b)
		}
		if i >= j {
			log.Panicf("passes %s and %s out of order", a, b)
		}
	}
}
